Add operation components, orchestrator, and emitter rendering#84
Closed
FionaBronwen wants to merge 29 commits into
Closed
Add operation components, orchestrator, and emitter rendering#84FionaBronwen wants to merge 29 commits into
FionaBronwen wants to merge 29 commits into
Conversation
4 tasks
79f53c2 to
41fbefb
Compare
3387127 to
1a5163c
Compare
41fbefb to
8a187c4
Compare
9e90b36 to
ffc3f0f
Compare
ffa9c95 to
fe7da60
Compare
ffc3f0f to
2763d30
Compare
Introduce the foundation for the component-based GraphQL emitter: - Build/config: Add @alloy-js/core, @alloy-js/graphql dependencies, configure JSX transpilation (tsconfig, vitest) - Context system: GraphQLSchemaContext with ClassifiedTypes, ModelVariants, and ScalarVariant interfaces - Field components: Field, OperationField, and GraphQLTypeExpression for rendering model properties and operations as GraphQL SDL - Type resolution: GraphQLTypeExpression handles scalars, models, enums, unions, arrays, and nullability using an isInput prop to distinguish input vs output context - Scalar mappings: Add getGraphQLBuiltinName() for built-in scalar identity checks
f73791c to
ce4affb
Compare
618e444 to
1f7438a
Compare
ce4affb to
98fb4f4
Compare
2ef9793 to
db9b099
Compare
98fb4f4 to
eb57da4
Compare
The ModelVariants lookup structure was originally designed to help components decide when to append "Input" suffix to model names. However, the mutation engine now fully handles this: - Input models are mutated with the "Input" suffix already applied - Property type references are rewired to point to the correct variants This commit removes the unused ModelVariants interface and the lookup logic from GraphQLTypeExpression, simplifying the component to just use the model's name directly. Also adds unionMembers to context (needed for union rendering).
db9b099 to
6cd9324
Compare
eb57da4 to
2be2c50
Compare
Remove unused utility functions that were superseded by the mutation engine: - getTemplatedModelName (mutation engine handles template naming) - getSingleNameWithNamespace (never used in production) - isArray, isRecordType, isScalarOrEnumArray, isUnionArray (use compiler's isArrayModelType) - unwrapModel, unwrapType (never imported) - isTrueModel (never imported) Also removes the corresponding test for getSingleNameWithNamespace.
6cd9324 to
671900f
Compare
2be2c50 to
c975212
Compare
255e13b to
15743c4
Compare
918f6cc to
cf1eb14
Compare
Removes the procedural GraphQL emitter and stands up the data-pipeline skeleton that the upcoming Alloy component-based emitter will build on. Deleted: - graphql-emitter.ts (old procedural emitter) - schema-emitter.ts (old schema-specific emitter) - registry.ts (old type registry) - type-maps.ts (old type mapping logic) - test/emitter.test.ts tests for the legacy emitter Rewrote src/emitter.ts as a four-phase data pipeline: - Phase 1: type usage tracking (reachability / input-output marking) - Phase 2: mutation (GraphQL naming via the mutation engine) - Phase 3: classification (interfaces, output models, input models, ops) - Phase 4: model variant lookups The pipeline produces a SchemaPipelineResult bundle (classifiedTypes, modelVariants, scalarSpecifications) that is handed to a renderSchema stub. The stub is a no-op in this PR; component-based SDL rendering, file emission, and the .tsx conversion land in follow-up PRs. Introducing the SchemaPipelineResult type and the pipeline/renderer seam here keeps the downstream PR reviewable on its own. Also introduces two new diagnostics that the pipeline reports today: - empty-schema: fires when a schema has no query root (GraphQL requires one) - void-operation-return: fires when an operation returns void (no GraphQL equivalent; the operation is filtered out of the schema) Test coverage added for both diagnostics in test/emitter.test.ts.
The emitter used to walk the schema namespace itself, bucket types by kind, dedup scalars, and run a second classification pass to split models into input/output variants. That loop also leaked implementation details (an `originalToMutated` back-map and a `TypeUsageResolver` that outlived mutation) into the emitter's public shape. Introduce `GraphQLMutationEngine.mutateSchema(schema, typeUsage)` that returns a fully-classified `MutatedSchema`: - Pre-bucketed models: `interfaces`, `outputModels`, `inputModels` - Pre-classified operations: `queries`, `mutations`, `subscriptions` - Derived metadata: `wrapperModels`, `scalarVariants`, `scalarSpecifications` Classification happens inline during the walk. `typeUsage` is consumed entirely inside the engine; the emitter hands it over and never touches it again. `originalToMutated` no longer exists outside the engine. Implementation lives in `src/mutation-engine/schema-mutator.ts` as a free function called from the `GraphQLMutationEngine` class to avoid a circular import between engine.ts and schema-mutator.ts. The emitter's `emitSchema` reduces to: resolve type usage, call `engine.mutateSchema`, report void-return diagnostics, check for empty schema, build variant lookups, hand off to the (stubbed) renderer.
After the TypeGraph refactor in PR #77, ScalarVariant was removed from the context module. Move it to schema-mutator.ts where it belongs since it's part of the MutatedSchema interface.
Add the first set of Alloy JSX components for GraphQL type emission: - EnumType: renders GraphQL enum definitions with member descriptions and deprecation - ScalarType: renders custom scalar definitions with @specifiedBy support - UnionType: renders union type definitions with model and scalar variant members - GraphQLSchema: root context wrapper providing TspContext and GraphQLSchemaContext Add component-level tests using renderSchema + printSchema to exercise the real Alloy rendering pipeline. Each component is tested in isolation with a lightweight test helper (renderComponentToSDL) that provides minimal context. 14 new tests covering: basic rendering, doc comments, deprecation, name sanitization, @specifiedBy, union member registration, and scalar wrappers.
- Extract isScalarLikeType() to type-utils.ts shared utility, replacing duplicated inline check in union-type.tsx - Replace unsafe `as Union` casts in union-type tests with assertUnionResult() helper that provides a clear error if the mutation returns a Model
Replace fragile .toContain() assertions with inline snapshots to match TSP ecosystem best practices. This makes test output more readable and maintainable by showing the complete expected SDL in one place.
The function was extracted from union-type.tsx in commit 6d47132e4 but the type-utils.ts hunk was dropped during the rebase due to conflicts with 'Remove dead code from type-utils.ts' in the base.
- ScalarType: take specificationUrl as prop instead of from context - component-test-utils: use minimal TypeGraph context - scalar-type.test: pass specificationUrl as prop
- Mutate operations in schema-mutator so type references point to correctly mutated types (input params → mutated input models, return types → mutated output models) - Fix nullable union replacement to mutate inner type with context (Address | null → AddressInput in input context) - Add Input suffix to models in input context with double-suffix prevention (BookInput stays BookInput, not BookInputInput) - Add comprehensive tests for input suffix, nullable union replacement, and operation type references
Models used as both input and output must be mutated separately: - Output context produces 'Foo' - Input context produces 'FooInput' Previously, all models were mutated with Output context only, causing duplicate type names when rendering both variants.
- Add withInputSuffix helper to prevent "PetInput" → "PetInputInput" - Fix oneOf input unions to mutate variant types with input context (model variants now correctly get their Input suffix, e.g. Cat → CatInput) - Refactor model.ts to use shared withInputSuffix helper
Add field-bearing type components that use the Field infrastructure (already present from the parent branch): - ObjectType: renders object types with fields, @compose interfaces, and @operationFields support - InterfaceType: renders interface type definitions with fields - InputType: renders input types with automatic Input suffix when a model appears in both input and output positions 17 new component tests covering: basic field rendering, doc comments, optional/nullable fields, array/list types, deprecated fields, interface implementation via @compose, and Input suffix logic.
…ed imports - Add explanatory comment on getComposition/iface.name in object-type.tsx clarifying that pre-mutation names match post-mutation names (mutation engine doesn't rename models) - Remove unused `type Model` import from all G2 test files
- Add @operationFields test to ObjectType (verifies operation field rendering) - Add nested model reference test to ObjectType (verifies type references) - Add empty model tests to ObjectType, InputType, InterfaceType (documents that GraphQL requires at least one field per type)
The mutation engine now handles all input/output type naming, so components can use type.name directly without looking up whether a model has both input and output variants.
- Remove implementation detail comments about mutation engine - Remove redundant tests that were testing non-responsibility of the component (InputType doesn't add suffixes - that's not its job)
Replace fragile .toContain() assertions with inline snapshots to match TSP ecosystem best practices. This makes test output more readable and maintainable by showing the complete expected SDL in one place.
Brings the component-based GraphQL emitter online by wiring the data pipeline from the foundation skeleton into Alloy JSX rendering. New components: - components/operations/query-type.tsx, mutation-type.tsx, subscription-type.tsx: render the three GraphQL root operation types - components/operations/index.ts: barrel export - components/type-collections.tsx: orchestrator that dispatches each classified-type bucket (scalars, enums, unions, interfaces, objects, inputs) into the appropriate leaf-type component Emitter wiring: - Rename src/emitter.ts → src/emitter.tsx - Phase 5 now renders via Alloy's renderSchema, converts to SDL with graphql-js printSchema, and writes the output via emitFile - Adds output-file option handling with interpolatePath Testing: - test/e2e.test.ts: single happy-path smoke test proving the full TypeSpec → mutation → classification → rendering → SDL pipeline works - Update the existing test/emitter.test.ts data-pipeline test to now assert that SDL output is produced (Phase 5 is wired up) Broader integration coverage (nullability, input/output splitting, unions, enums, arrays, circular refs, deprecation, doc comments) lands in a follow-up PR focused on tests.
Adds component tests for QueryType, MutationType, and SubscriptionType using inline snapshots. Tests cover rendering with scalar return types, model return types (with stub type registration), parameters, and empty operation lists. Updates renderComponentToSDL utility to support skipPlaceholderQuery option for testing QueryType, while maintaining backwards compatibility with existing tests that pass context overrides directly. Also cleans up e2e and emitter tests to use consistent vitest expect() assertions with toMatchInlineSnapshot() instead of mixed strictEqual and .includes() checks.
- type-collections: components take data via props instead of context - emitter: pass MutatedSchema data directly to collection components - Use minimal TypeGraph context (just globalNamespace) This aligns with the TypeGraph design where context is minimal and components receive their data through props.
ccdc78f to
372c7da
Compare
8ac0071 to
c42edc5
Compare
372c7da to
c63fd39
Compare
c63fd39 to
16e9fe3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
QueryType,MutationType,SubscriptionType(incomponents/operations/) render the three GraphQL root operation typescomponents/type-collections.tsxdispatches each classified-type bucket (scalars, enums, unions, interfaces, objects, inputs) into the appropriate leaf-type componentsrc/emitter.ts→src/emitter.tsx. Phase 5 now renders via Alloy'srenderSchema, converts to SDL withgraphql-jsprintSchema, and writes the output viaemitFile. Addsoutput-fileoption handling withinterpolatePath.test/components/operation-types.test.tsxwith 10 tests covering QueryType, MutationType, and SubscriptionType renderingtest/e2e.test.tsandtest/emitter.test.tswith snapshot assertions proving the full TypeSpec → mutation → classification → rendering → SDL pipeline worksTest plan
pnpm buildsucceedspnpm test— 193 tests pass